package com.ease.framework {

import com.ease.util.Diagnostics;
import com.ease.util.MixedUtil;
import com.ease.util.ObjectEvent;
import com.ease.util.ReflectionUtil;
import com.ease.util.RelationshipUpdateEvent;
import com.ease.util.ServiceApplicabilityUpdateEvent;
import com.ease.util.Set;
import com.ease.util.TaggedPropertyInfo;
import com.ease.util.reflection.TagArgReflectionInfo;
import com.ease.util.gc.SharedReference;

import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;

import mx.binding.utils.BindingUtils;
import mx.binding.utils.ChangeWatcher;
import mx.core.FlexGlobals;
import mx.core.UIComponent;
import mx.events.FlexEvent;
import mx.managers.SystemManager;


/**
 * Provides a generic context propagation service. Context propagation means passing down
 * of some data recursively to the nested objects. This pattern occurs often with views,
 * where all the descendant views of some root need to inherit data from the root view.
 * By taking care of it in the framework through the use of declarative tags, Frinje
 * frees up the application from the associated tedius plumbing.
 *
 * <p>This class depends on object registries (i.e., instances of
 * IInjectionTargetRegistry) to supply the objects that are targets for context
 * inheritance. The source properties for such data are discovered by recursively looking
 * at the "parent" property of the target.
 *
 * <p>The following example shows how a parent object's class can declare a property as
 * [Inheritable]:
 * <listing version="3.0">
 * class SomeParentObjectClass ...
 * {
 *    [Inheritable]
 *    [Bindable]
 *    public var someData:SomeType;
 *    ...
 * }
 * </listing>
 *
 * This works equally well with property accessors. Also, the [Bindable] tag above is
 * optional, although without it, any changes in the property value at the source object
 * would not be reflected in the targets.
 *
 * <p>The declaration can also specify the type of the child objects to which the
 * inheritance of the given property should be restricted:
 * <listing version="3.0">
 *    [Inheritable(childType="some.ns.ISomeChildType")]
 * </listing>
 *
 * <p>The declaration can also specify a different property name to project onto in the
 * child objects, by using the <code>targetProperty</code> parameter:
 * <listing version="3.0">
 *    [Inheritable(targetProperty="somePropertyThatIsNot_someData")]
 *    public var someData:SomeType;
 * </listing>
 *
 * <p>An [Inheritable] property is propagated to a child only if the declared property
 * type in the child is assignable <i>from</i> the property type in the parent.
 * Propagation also occurs if the child property type is assignable <i>to</i>
 * the parent property type and the child property is explicitly marked as [Inherited].
 *
 * <p>A nested or descendant object can opt out of the property propagation if its class
 * declares the property as non-inherited using the following syntax:
 * <listing version="3.0">
 * class SomeChildObjectClass ...
 * {
 *    [Inherited("false")]
 *    public var someData:SomeType;
 *    ...
 * }
 * </listing>
 *
 * <p>A nested or descendant object can mark its properties as [Inherited] to explicitly
 * indicate that the said properties should be set by the object's parent (or container
 * or owner). The [Inheritable] tag then provides an easy way for an ancestor to
 * propagate these properties to its descendants by defining those properties on itself
 * and marking them with the tag.
 *
 * <p>An object can opt-out or be excluded from context propagation by raising a
 * ServiceApplicabilityUpdateEvent such that the specified service id is
 * "ContextPropagation". This event can be raised either on some object that's supposed
 * to be on the associated event bus or it can be raised on the SystemManager.
 *
 * <p>A client can raise a RelationshipUpdateEvent (with "parent" as its relationship
 * property's value) to register a custom object-parent binding (i.e., the parent object
 * thus registered may be different from the value of the object's parent property, or the
 * object may not even have a parent property). This event can be raised either on some
 * object that's supposed to be on the associated event bus or it can be raised on the
 * SystemManager.
 *
 * <p>In addition to binding properties on the object itself, context propagation also
 * binds the eligible properties on the object's mediator (if any such mediator exists, as
 * defined by the ViewRegistry). Note that this works fully only if the mediators
 * corresponding to the view parental chain each has a property tagged as [View] or
 * [InjectableView] that represents the associated view.
 *
 * <p>In general, a view-mediator pair is treated as a single unit by this service. For
 * example, if the service is enabled/disabled on a view, it's implicitly enabled/disabled
 * on the associated mediator as well; the vice-versa also applies. The [Inheritable]
 * properties on either of the two objects in a view-mediator pair are treated as
 * properties of the pair, and are propagated to both the view and the mediator child
 * hierarchies.
 *
 * @see ViewRegistry
 */
public class ContextPropagationManager
{
   private static var _instance:ContextPropagationManager;
   private var _eventBusRef:SharedReference = SharedReference.newReference(EventBus);
   private var _objsUnderWatch:Dictionary = new Dictionary( true );
   private var _svcApplicabilityRegistry:ServiceApplicabilityRegistry;
   /** Instances of the types within these namespaces are excluded as source or target of
    *  property propagation. */
   private var _excludedTypePrefixes:Array/*String*/ = [];

   /**
    * The id of the parent role, which can be used in a RelationshipUpdateEvent.
    *
    * @see com.ease.flexutil.RelationshipUpdateEvent#relationship
    */
   public static const PARENT_ROLE_ID:String = "parent";

   public static const SERVICE_ID:String = "ContextPropagation";

   /**
    * A global instance of this class. This is the instance that would typically be used
    * by an application.
    *
    * @return
    *    An instance of ContextPropagationManager for global use.
    */
   public static function get instance():ContextPropagationManager {
      if (_instance == null) {
         _instance = new ContextPropagationManager( EventBus.instance );
         _instance.globalInstanceInit();
      }
      return _instance;
   }

   /**
    * Initializes an instance of this class.
    *
    * @param eventBus
    *    An optional EventBus instance associated with this service, used for the
    *    purposes of listening to events like RelationshipUpdateEvent or
    *    ServiceApplicabilityUpdateEvent.
    */
   public function ContextPropagationManager( eventBus:EventBus = null ) {
      this.eventBus = eventBus;
      _svcApplicabilityRegistry = new ServiceApplicabilityRegistry();
      _svcApplicabilityRegistry.associatedService = this;
      if (this.eventBus == null) {
         return;
      }
      // We listen for the service enable/disable requests on the associated event bus.
      // The use of service enable/disable here is to allow the client to enable/disable
      // "ContextPropagation" selectively on individual objects.
      this.eventBus.addEventListener(
            ServiceApplicabilityUpdateEvent.SERVICE_ENABLE_REQUESTED,
            onServiceEnabledDisabled );
      this.eventBus.addEventListener(
            ServiceApplicabilityUpdateEvent.SERVICE_DISABLE_REQUESTED,
            onServiceEnabledDisabled );
   }

   //---------------------------------------------------------------------------
   // Instance Methods

   /**
    * Adds the given registry as a notifier of injectable (target) objects.
    */
   public final function addTargetRegistry( registry:IObjectModelSource ):void {
      registry.addEventListener( ObjectEvent.OBJECT_ADDED,
                                 onInjectableObjectAddedRemoved );
      registry.addEventListener( ObjectEvent.OBJECT_REMOVED,
                                 onInjectableObjectAddedRemoved );
      registry.addEventListener( RelationshipUpdateEvent.ROLE_ASSIGNED,
                                 onRoleAssignment );
   }

   /**
    * Removes the given registry as a notifier of injectable (target) objects.
    */
   public final function removeTargetRegistry( registry:IObjectModelSource ):void {
      registry.removeEventListener( ObjectEvent.OBJECT_ADDED,
                                    onInjectableObjectAddedRemoved );
      registry.removeEventListener( ObjectEvent.OBJECT_REMOVED,
                                    onInjectableObjectAddedRemoved );
      registry.removeEventListener( RelationshipUpdateEvent.ROLE_ASSIGNED,
                                    onRoleAssignment );
   }

   /**
    * Adds a type name prefix for the classes whose instances should be excluded as
    * sources or targets of this service (i.e., property propagation). The default values
    * are "mx." and "flash.".
    */
   public final function addExcludedTypePrefix( prefix:String ):void {
      if (_excludedTypePrefixes.indexOf( prefix ) >= 0) {
         return;
      }
      _excludedTypePrefixes.push( prefix );
   }

   private function globalInstanceInit():void {
      var app:EventDispatcher = FlexGlobals.topLevelApplication as EventDispatcher;
     // The global instance of this class listens for the service enable/disable request
     // events on the Application object as well, i.e., not just the associated event bus
     // (so that even those objects that are not already on some event bus can also raise
     // such events).
     // Note that we don't (need to) use weak-referencing here (i.e., when adding the
     // listeners) because this global instance is for the lifetime of the app.
     // Also note that multiple notifications (i.e., from the bus as well as from the
     // system manager, if that ever happens) are fine since object add/remove in
     // this class is idempotent.
     app.addEventListener(
           ServiceApplicabilityUpdateEvent.SERVICE_ENABLE_REQUESTED,
           onServiceEnabledDisabled );
     app.addEventListener(
           ServiceApplicabilityUpdateEvent.SERVICE_DISABLE_REQUESTED,
           onServiceEnabledDisabled );
   }

   private function onServiceEnabledDisabled( ev:ServiceApplicabilityUpdateEvent ):void {
      if (ev.serviceId != SERVICE_ID) {
         return;
      }
      var object:Object = ev.object;
      if (! MixedUtil.isIterableCollection( object )) {
         enableDisableServiceFor( object, ev );
         return;
      }
      for each (var item:Object in object) {
         enableDisableServiceFor( item, ev );
      }
   }

   /**
    * Enables/Disables the context/property propagation service on the given component
    * object.
    */
   private function enableDisableServiceFor( object:Object,
                                             ev:ServiceApplicabilityUpdateEvent ):void {
      if (object == null) {
         return;
      }
      if (_svcApplicabilityRegistry.isPermanentlyDisabled( object ) &&
          ev.type == ServiceApplicabilityUpdateEvent.SERVICE_ENABLE_REQUESTED) {
         return;
      }
      var opCode:String = eventIdToOpCode( ev.type );

      // We add unconditionally in the following, i.e., without checking for the presence
      // of the object in the exclusion set so that then the event can be used to enable
      // the service on an object that may be previously unknown to the service.

      var pair:ViewMediatorPair = toViewMediatorPair( object );
      if (! pair.isValid) {
         _svcApplicabilityRegistry.applyChange( object, ev );
         addRemoveObject( object, opCode );
         return;
      }
      _svcApplicabilityRegistry.applyChange( pair.view, ev );
      _svcApplicabilityRegistry.applyChange( pair.mediator, ev );
      addRemoveObject( pair.view, opCode );
      addRemoveObject( pair.mediator, opCode );
   }

   private static function eventIdToOpCode( eventTypeId:String ):String {
      switch (eventTypeId) {
      case ServiceApplicabilityUpdateEvent.SERVICE_ENABLE_REQUESTED:
         return ObjectEvent.OBJECT_ADDED;
      case ServiceApplicabilityUpdateEvent.SERVICE_DISABLE_REQUESTED:
         return ObjectEvent.OBJECT_REMOVED;
      default:
         return null;
      }
   }

   private function removeTarget( target:* ):void {
      if (target == null) {
         return;
      }
      var bindingInfo:PropertyBindingInfo = _objsUnderWatch[target];
      if (bindingInfo == null) {
         return;
      }
      bindingInfo.reset();
      delete _objsUnderWatch[target];
   }

   private function onInjectableObjectAddedRemoved( ev:ObjectEvent ):void {
      var opCode:String = ev.type;
      addRemoveObject( ev.object, opCode );
   }

   /**
    * Adds or removes the given object for context propagation purposes.
    *
    * @param opCode
    *     Use ObjectEvent.OBJECT_ADDED for add, ObjectEvent.OBJECT_REMOVED for remove.
    */
   private function addRemoveObject( object:Object, opCode:String ):void {
      if (object == null) {
         return;
      }
      if (opCode == ObjectEvent.OBJECT_REMOVED) {
         removeTarget( object );
         return;
      }
      if (opCode == ObjectEvent.OBJECT_ADDED) {
         addTarget( object );
         return;
      }
   }

   private function addTarget( object:Object ):void {
      if (isTargetExcludedFromPropagation( object ) ||
          bindingsAlreadyExistFor( object )) {
         return;
      }

      // See if it's a view and whether it's been "initialized" or not; if not, give it a
      // chance to complete initialization before causing its context setters to be
      // invoked.
      if (object is UIComponent) {
         var view:UIComponent = UIComponent( object );
         if (! view.initialized) {
            var viewInitCallback:Function = function( ev:Event = null ):void {
               view.removeEventListener( FlexEvent.CREATION_COMPLETE, viewInitCallback );
               addTarget( view );
            }
            view.addEventListener( FlexEvent.CREATION_COMPLETE, viewInitCallback );
            return;
         }
      }

      bindObjectToParentOf( object );
      bindParentalMediatorChainFor( object );

      // No need to recurse on the children, the source of the objects is supposed to
      // notify on all objects, even if deeply nested.
   }

   private function isTargetExcludedFromPropagation( object:Object ):Boolean {
      // Note: Even though a view-mediator pair is treated like a single unit, do *not*
      // check for the paired object here (in the view case); the pairing might not have
      // existed when the first of the two objects was notified, hence we need to treat
      // them as individuals here.

      if (isOfExcludedType( object )) {
         return true;
      }
      if (_svcApplicabilityRegistry.isServiceEnabledFor( object )) {
         return false;
      }
      return true;
   }

   private function bindingsAlreadyExistFor( object:Object ):Boolean {
      // Note: Even though a view-mediator pair is treated like a single unit, do *not*
      // check for the paired object here (if any); the two objects may get registered
      // with this service at different times, and the pairing might not have existed at
      // the time the first of the two objects was registered, so we need to treat them
      // as individuals here.
      var bindingInfo:PropertyBindingInfo = _objsUnderWatch[object];
      if (bindingInfo != null && bindingInfo.boundPropWatchers != null) {
         // bindings have already been set up on this object
         return true;
      }
      return false;
   }

   private function bindObjectToParentOf( objectToBind:*,
                                          childWhoseParentToUse:* = undefined,
                                          propsAlreadyBound:Set = null ):void {
      // Note: Do *not* trivially return from here even if there already exist some
      // bindings for this object; this method may be called for each parent in the
      // parental chain, or for a custom parent assignment (in addition to the parental
      // chain).

      if (childWhoseParentToUse == undefined) {
         childWhoseParentToUse = objectToBind;
      }
      var parent:* = getParent( childWhoseParentToUse );
      if (parent == null || parent == objectToBind) {
         return;
      }
      if (propsAlreadyBound == null) {
         propsAlreadyBound = new Set();
      }
      if (isOfExcludedType( parent )) {
         // skip to parent's parent
         bindObjectToParentOf( objectToBind, parent, propsAlreadyBound );
         return;
      }
      bindToParent_Recurse( objectToBind, parent, propsAlreadyBound );
   }

   private function
   bindToParent_Recurse( object:*, parent:*, propsAlreadyBound:Set = null ):void {
      if (object == null || parent == null) {
         return;
      }
      if (propsAlreadyBound == null) {
         propsAlreadyBound = new Set();
      }
      bindToParent( object, parent, propsAlreadyBound );

      // Also bind the object to the parent's mediator (if parent is a view) or parent's
      // view (if parent is a mediator).
      var parentPair:ViewMediatorPair = toViewMediatorPair( parent );
      if (parentPair.view != parent) {
         bindToParent( object, parentPair.view, propsAlreadyBound );
      }
      if (parentPair.mediator != parent) {
         bindToParent( object, parentPair.mediator, propsAlreadyBound );
      }

      // recurse up the parent chain
      bindObjectToParentOf( object, parent, propsAlreadyBound );
   }

   private function bindToParent( object:*, parent:*, propsAlreadyBound:Set ):void {
      if (object == null || parent == null) {
         return;
      }
      var parentBindingInfo:PropertyBindingInfo = _objsUnderWatch[parent];
      if (parentBindingInfo == null) {
         parentBindingInfo = new PropertyBindingInfo();
         _objsUnderWatch[parent] = parentBindingInfo;
      }
      if (parentBindingInfo.inheritableProps == null) {
         if (isOfExcludedType(parent)) {
            parentBindingInfo.inheritableProps = [];
         } else {
            parentBindingInfo.inheritableProps =
                  ReflectionUtil.getAllKeyValuesForTaggedProperties( parent, "Inheritable" );
         }
      }
      var propInfos:Array/*TaggedPropertyInfo*/ = parentBindingInfo.inheritableProps;
      if (propInfos.length > 0) {
         createBindings( object, parent, propInfos, propsAlreadyBound );
      }
   }

   private function createBindings( child:*, parent:*, props:Array,
                                    propsAlreadyBound:Set ):void {
      if (props == null || props.length == 0) {
         return;
      }
      var propWatchers:Array = [];
      for each (var pi:TaggedPropertyInfo in props) {
         var srcPropName:String = pi.name;
         var targetPropName:String =
               getInheritanceQualifiedChildProperty( child, parent, pi );
         if (targetPropName == null || propsAlreadyBound.contains( targetPropName )) {
            continue;
         }
         var changeWatcher:ChangeWatcher =
               MixedUtil.bindProperty( child, targetPropName, parent, srcPropName );
         propWatchers.push( changeWatcher );
         propsAlreadyBound.add( targetPropName );
      }
      if (propWatchers.length == 0) {
         return;
      }
      var bindingInfo:PropertyBindingInfo = _objsUnderWatch[child];
      if (bindingInfo == null) {
         bindingInfo = new PropertyBindingInfo();
         _objsUnderWatch[child] = bindingInfo;
      }
      if (bindingInfo.boundPropWatchers == null) {
         bindingInfo.boundPropWatchers = propWatchers;
      } else {
         var preExistingWatchers:Array = bindingInfo.boundPropWatchers;
         bindingInfo.boundPropWatchers = preExistingWatchers.concat( propWatchers );
      }
   }

   /**
    * The given object can either be a view, or a mediator. If neither of these, it's
    * ignored.
    */
   private function bindParentalMediatorChainFor( object:* ):void {
      var pair:ViewMediatorPair = toViewMediatorPair( object );
      var view:DisplayObject = pair.view;
      var mediator:Object = pair.mediator;
      if (mediator == null || bindingsAlreadyExistFor( mediator )) {
         return;
      }
      // The view hierarchy is traversed in the following because the mediator-to-view
      // relationships may not yet be established, so we can't traverse from mediator to
      // its parent mediators/views.

      var parents:Array = [];
      while (view != null && !(view is SystemManager)) {
         var parentView:DisplayObject = view.parent;
         var parentMediator:Object = ViewRegistry.getMediator( parentView );
         if (parentMediator != null) {
            parents.push( parentMediator );
         }
         if (parentView != null) {
            // a mediator inherits both from its parent mediators and from parent views
            parents.push( parentView );
         }
         view = parentView;
      }
      var propsAlreadyBound:Set = new Set();
      for each (var parent:Object in parents) {
         bindToParent( mediator, parent, propsAlreadyBound );
      }
   }

   /**
    * Looks up the parent of the given object through its "parent" property; if the
    * property doesn't exist and if the object is a mediator for some view (as defined by
    * ViewRegistry), then it returns the parent mediator by way of the view parent.
    */
   private static function getParent( object:Object ):Object {
      if (object == null) {
         return null;
      }
      var parent:Object;
      if (object.hasOwnProperty( "parent" )) {
         parent = object["parent"];
         if (parent != null) {
            return parent;
         }
      }
      var pair:ViewMediatorPair = toViewMediatorPair( object );
      if (pair.mediator != object) {
         return null;
      }
      var parentView:DisplayObject = getParent( pair.view ) as DisplayObject;
      var parentPair:ViewMediatorPair = toViewMediatorPair( parentView );
      return parentPair.mediator;
   }

   /** Returns a non-null value even if the given object is null. */
   private static function toViewMediatorPair( object:Object ):ViewMediatorPair {
      var view:DisplayObject;
      var mediator:Object;
      if (object is DisplayObject) {
         view = object as DisplayObject;
         mediator = ViewRegistry.getMediator( view );
      } else {
         view = ViewRegistry.getViewFromMediator( object );
         if (view != null) {
            mediator = object;
         }
      }
      return new ViewMediatorPair( view, mediator );
   }

   /**
    * @param srcPropInfo
    *    Represents a property on the parent which might be eligible for inheritance by
    *    the given child. The method does not check whether the property on the parent is
    *    tagged as [Inheritable] or not; that part is just assumed to hold true.
    */
   private function
   getInheritanceQualifiedChildProperty( child:*, parent:*,
                                         srcPropInfo:TaggedPropertyInfo ):String {
      var parentDefinedPropType:Class = srcPropInfo.propertyType;
      var allowedChildType:String = null;
      var targetPropName:String = srcPropInfo.name; // the default case
      Diagnostics.assert( srcPropInfo.tagArgs != null,
            "tagArgs assumed non-null + to contain all args supplied to [Inheritable]" );
      if (srcPropInfo.tagArgs.length == 0) {
         // If there are no args to the [Inheritable] tag, the check is only on the
         // property name and property type (and not on the child object's type).
         allowedChildType = "Object";
         if (isQualifiedTarget( child, targetPropName, allowedChildType,
                                parent, parentDefinedPropType )) {
            return targetPropName;
         }
         return null;
      }
      for each (var arg:TagArgReflectionInfo in srcPropInfo.tagArgs) {
         var argKey:String = arg.key;
         var argVal:String = arg.value;
         if (argKey == "childType") {
            // process the current value of allowedChildType before overwriting it
            if (isQualifiedTarget( child, targetPropName, allowedChildType,
                                   parent, parentDefinedPropType )) {
               return targetPropName;
            }
            allowedChildType = argVal;
            targetPropName = srcPropInfo.name; // revert to default
            continue;
         }
         if (argKey == "targetProperty") {
            // Marks the completion of a (childType="foo", targetProperty="bar") style
            // pair (such that only one of the two parameters might have been specified).
            targetPropName = argVal;
            if (allowedChildType == null) {
               allowedChildType = "Object";
            }
            if (isQualifiedTarget( child, targetPropName, allowedChildType,
                                   parent, parentDefinedPropType )) {
               return targetPropName;
            }
            allowedChildType = null; // reset
            targetPropName = srcPropInfo.name; // revert to default
            continue;
         }
      }
      // For the case where only a childType name is specified, without a corresponding
      // targetProperty name.
      if (isQualifiedTarget( child, targetPropName, allowedChildType,
                             parent, parentDefinedPropType )) {
         return targetPropName;
      }
      return null;
   }

   private function isQualifiedTarget( child:*, targetPropName:String,
                                       allowedChildTypeName:String, parent:*,
                                       parentDefinedPropType:Class ):Boolean {
      if (allowedChildTypeName == null) {
         // can't certify as qualified
         return false;
      }
      if (isPositivelyDisqualifiedTarget( child, targetPropName,
                                          parentDefinedPropType )) {
         return false;
      }
      var allowedChildType:Class =
            ReflectionUtil.getDefinition( allowedChildTypeName, parent );
      if (allowedChildType != null && child is allowedChildType) {
         // If the child is compatible with a type that parent declared as a target for
         // value-inheritance of this property, then it is eligible for inheritance
         // without any hesitation.
         return true;
      }
      return false;
   }

   private function isPositivelyDisqualifiedTarget( object:*, propName:String,
                                                    parentPropType:Class ):Boolean {
      if (! object.hasOwnProperty( propName )) {
         return true;
      }
      var access:String =
            ReflectionUtil.getPropertyAccessModifier( propName, object );
      if (access == null || access == "readonly") {
         // The property cannot be set, hence can't inherit the value from the parent.
         return true;
      }
      var isChildPropMarkedOut:Boolean =
            ReflectionUtil.isPropertyTaggedWith( propName, object, "Inherited", "false" );
      if (isChildPropMarkedOut) {
         return true;
      }
      // Now check if the two properties are type-compatible in either direction.
      var childPropType:Class =
            ReflectionUtil.getDeclaredPropertyType( propName, object );
      var parentPropTypeAssignableToChildPropType:Boolean =
            ReflectionUtil.isTypeCompatibleWith( parentPropType, childPropType )
      var childPropTypeAssignableToParentPropType:Boolean =
            ReflectionUtil.isTypeCompatibleWith( childPropType, parentPropType );
      if (! parentPropTypeAssignableToChildPropType) {
         if (! childPropTypeAssignableToParentPropType) {
            // If neither property is type-compatible with the other, we can assume they
            // were not meant for eachother.
            return true;
         }
         var isChildPropMarkedIn:Boolean =
               ReflectionUtil.isPropertyTaggedWith( propName, object, "Inherited" );
         if (! isChildPropMarkedIn) {
            // If the child property is not positively marked [Inherited] then
            // child-to-parent property type assignability is not enough.
            return false;
         }
      }
      // we don't positively know if the property is disqualified for inheritance or not
      return false;
   }

   private function onRoleAssignment( ev:RelationshipUpdateEvent ):void {
      if (ev.relationship == ViewRegistry.MEDIATOR_ROLE_ID) {
         onMediatorAssignment( ev );
         return;
      }
      if (ev.relationship == PARENT_ROLE_ID) {
         onParentAssignment( ev );
         return;
      }
   }

   private function onParentAssignment( ev:RelationshipUpdateEvent ):void {
      // There can be only one parent at a time, hence remove the current bindings, and
      // add new bindings (associating the object to its new parent).
      removeTarget( ev.object );
      addTarget( ev.object );
      // It may be a custom parent assignment, i.e., the relatedObject may not be the
      // value of the "parent" property on the given object; hence, additionally bind to
      // the provided parent.
      bindToParent_Recurse( ev.object, ev.relatedObject );
   }

   private function onMediatorAssignment( ev:RelationshipUpdateEvent ):void {
      // We handle only updates or changes in the mediator assignment (i.e.,
      // oldRelatedEntity should be non-null). The original relationship assignment must
      // have been taken care of at the time when the objects in question were first
      // discovered/registered.
      if (ev.oldRelatedObject == null) {
         return;
      }
      // There can be only one mediator at a time, hence remove the current bindings, and
      // add new bindings.
      removeTarget( ev.object );
      removeTarget( ev.relatedObject );
      removeTarget( ev.oldRelatedObject );
      addTarget( ev.object );
      addTarget( ev.relatedObject );
      addTarget( ev.oldRelatedObject );
   }

   private function isOfExcludedType( object:Object ):Boolean {
      var className:String = getQualifiedClassName( object );
      for each (var forbiddenPrefix:String in _excludedTypePrefixes) {
         if (className.indexOf( forbiddenPrefix ) == 0) {
            return true;
         }
      }
      var isExcluded:Boolean = ReflectionUtil.isFlexTypeName(className);
      return isExcluded;
   }

   //------------------------------------------------------------------------------------
   // Instance properties
   // The property provider used avoids directly referencing the global instance.
   // This is done to simplify the object graph for Memory Profiler.

   private function get eventBus():EventBus {
      return _eventBusRef.object;
   }

   private function set eventBus(value:EventBus):void {
      _eventBusRef.object = value;
   }
} // class

}

import mx.binding.utils.ChangeWatcher;
import flash.display.DisplayObject;

class PropertyBindingInfo
{
   [ArrayElementType("mx.binding.utils.ChangeWatcher")]
   public var boundPropWatchers:Array;

   [ArrayElementType("com.ease.util.TaggedPropertyInfo")]
   public var inheritableProps:Array;

   public final function reset():void {
      if (boundPropWatchers == null) {
         return;
      }
      for each (var watcher:ChangeWatcher in boundPropWatchers) {
         watcher.unwatch();
      }
      boundPropWatchers = null;
   }
}

class ViewMediatorPair
{
   public var view:DisplayObject;
   public var mediator:Object;

   public function ViewMediatorPair( view:DisplayObject, mediator:Object ) {
      this.view = view;
      this.mediator = mediator;
   }

   /** Returns true if it's a non-null view-mediator pair. */
   public function get isValid():Boolean {
      return this.view != null && this.mediator != null;
   }
}
